
# put terrain and other actors in layer 'actors'.
# references must be in another layer

# chopped down squirtle_core to get actors's position and other potential
# actor's params.

##mejor aqui de cada color indicado devolvemos cx,cy, width, height
##se puede aceptar flag indicando gl coords or pygame coords
##se puede aceptar flag bWarnUnknowns para devolver una lista de colores
##detectados que no estan en la tabla.
##mejor aun, no recibir nada de eso, devolvemos tuplas (color, cx, cy, width, height)
# must detect:
#   a terrain reference rect (used to compensate offsets in actor position)
#   a group with id actors
from xml.etree.cElementTree import parse as cTree_parse
import re
import math
import sys
from squirtle_mod.svg_colors import svg_named_colors
    
    # note: in inkscape use one layer exclusively for actors.
    # Then you must give the id 'actors' to that layer.
    # Note that the layer name you see in the inkscape layers tool is not the id.
    # A simple method to set the layer id in inkscape:
    #   1. from the menu select layer | layers... ( capa | capas... in spanish)
    #   2. in the layers tool window that opens, select the layer with the
    #      actors, and give it some name, say zzz
    #   3. push the button to show the XML, at one level below the root the layers
    # will show. Look at the one with 'inkscape:label="zzz"' field,select it.
    #   4. In the lower right corner of XML view you can edit the id; put
    # 'actors' without return ( click accept / aceptar to set the new value)
    #   5. save the file.
    # Note: for faster parse it is convenient that the desired plane be near the
    # top of the list in the XML view (untested)
    # as an alternative, you could copy actors and terrain reference to an
    # empty svg.
    # Note: alpha matters for the color matching. Be sure not only at the
    # elements props the color match, but also that you dont have a group
    # or layer transparency that will modify the final color.

# para cuando deseamos por algun motivo obtener entidades en diferentes layers,
# podriamos recibir una lista de targets y entregar listas separadas de
# resultados. Pero esto en realidad se puede simular con llamadas consecutivas;
# si no hay una cuestion de performance de por medio entonces no convendria
# complicarse. ( quizas solamente separar la llamada a etree que es la mas
# costosa)
    
# este seria un caso particular. puede quedar aqui como muestra
def get_actors(fname, terrain_reference_color):
    group_id_target = 'actors'
    #terrain_reference_color = (164,106,158,92)
    provider = SVG_special(fname)
    print 'terrain_ref_color:', terrain_reference_color 
    provider.set_target(group_id_target, terrain_reference_color)
    return provider.get_results()

def parse_list(string): #? needs r".." ?
    return re.findall("([A-Za-z]|-?[0-9]+\.?[0-9]*(?:e-?[0-9]*)?)", string)

def parse_style(string):
    sdict = {}
    for item in string.split(';'):
        if ':' in item:
            key, value = item.split(':')
            sdict[key] = value
    return sdict
    
def parse_color(c, default=None):
    if not c:
        return default
    if c == 'none':
        return None
    if c[0] == '#': c = c[1:]
    if c.startswith('url(#'):
        return c[5:-1]
    if c in svg_named_colors:
        r,g,b = svg_named_colors[c]
        return [r,g,b,255]
    try:
        if len(c) == 6:
            r = int(c[0:2], 16)
            g = int(c[2:4], 16)
            b = int(c[4:6], 16)
        elif len(c) == 3:
            r = int(c[0], 16) * 17
            g = int(c[1], 16) * 17
            b = int(c[2], 16) * 17
        else:
            raise Exception("Incorrect length for colour " + str(c) + " length " + str(len(c)))            
        return [r,g,b,255]
    except Exception, ex:
        print 'Exception parsing color', ex
        return None
        
class Matrix(object):
    def __init__(self, string=None):
        self.values = [1, 0, 0, 1, 0, 0] #Identity matrix seems a sensible default
        if isinstance(string, str):
            if string.startswith('matrix('):
                self.values = [float(x) for x in parse_list(string[7:-1])]
            elif string.startswith('translate('):
                x, y = [float(x) for x in parse_list(string[10:-1])]
                self.values = [1, 0, 0, 1, x, y]
            elif string.startswith('scale('):
                sx, sy = [float(x) for x in parse_list(string[6:-1])]
                self.values = [sx, 0, 0, sy, 0, 0]           
        elif string is not None:
            self.values = list(string)
    
    def __call__(self, other):
        return (self.values[0]*other[0] + self.values[2]*other[1] + self.values[4],
                self.values[1]*other[0] + self.values[3]*other[1] + self.values[5])
    
    def inverse(self):
        d = float(self.values[0]*self.values[3] - self.values[1]*self.values[2])
        return Matrix([self.values[3]/d, -self.values[1]/d, -self.values[2]/d, self.values[0]/d,
                       (self.values[2]*self.values[5] - self.values[3]*self.values[4])/d,
                       (self.values[1]*self.values[4] - self.values[0]*self.values[5])/d])

    def __mul__(self, other):
        a, b, c, d, e, f = self.values
        u, v, w, x, y, z = other.values
        return Matrix([a*u + c*v, b*u + d*v, a*w + c*x, b*w + d*x, a*y + c*z + e, b*y + d*z + f])
                               

class SVG_special(object):
    """ get pigeons and gate params 
    
        `filename`: str
            The name of the file to be loaded.
        `target_color4`: 4-tuple with the rgba color target
        Note:  red<->pigeons, green<-> goal gate, white<-> reference rectangle

        Usage:
            positions, goal_pos, goal_height = SVG_get_special(params).get_result()
        where:
            position a list of (cx,cy), cx,cy = center of matching rect
            goal_pos : center position of goal actor
            goal_height : height of goal actor
    """
    def __init__(self, filename):
        self.filename = filename
        if open(filename, 'rb').read(3) == '\x1f\x8b\x08': #gzip magic numbers
            import gzip
            f = gzip.open(filename, 'rb')
        else:
            f = open(filename, 'rb')
        self.tree = cTree_parse(f) #cElementTree
        f.close()
    
    def set_target(self, group_id_target, terrain_color4):
        self.objects_by_color = {}
        self.group_id_target = group_id_target
        self.terrain_color4 = terrain_color4
        self.target_found = False
        self.targeting_state = 'find'
        self.pos = []
        self.parse_doc()
#        self.objects = []

    def get_results(self):
        try:
            offset = self.offset
        except AttributeError:
            print 'Not found reference terrain'
            raise
        q = complex(0.0,self.h_ref)
        def ch_coords(p):
            return (p-offset).conjugate()+q
        actors_by_color = {}
        for color4 in self.objects_by_color:
            actors_by_color[color4] = []
            for p, width, height in self.objects_by_color[color4]:
                pos = ch_coords(p)                
                actors_by_color[color4].append((pos,width,height))
        return actors_by_color
        

    # users dont need to call any of the following
    def parse_doc(self):
        self.paths = []
        try:
            self.width = self.parse_float(self.tree._root.get("width", '0'))
        except ValueError:
            #? happens by example when including units, like '12cm'
            self.width = 0
        try:
            #? happens by example when including units, like '12cm'
            self.height = self.parse_float(self.tree._root.get("height", '0'))
        except ValueError:
            self.height = 0
        if self.height:
            self.transform = Matrix([1, 0, 0, -1, 0, self.height])
        else:
            x, y, w, h = (self.parse_float(x) for x in parse_list(self.tree._root.get("viewBox")))
            self.transform = Matrix([1, 0, 0, -1, -x, h + y])
            self.height = h
            self.width = w
        self.opacity = 1.0
        for e in self.tree._root.getchildren():
            g_deep = 0
            if self.target_found:
                break
            try:
                self.parse_element(e)
            except Exception, ex:
                print 'Exception while parsing element', e
                print 'elem: %s'%e
                raise

    def parse_element(self, e):
        target_mark = False
##        print 'id:',e.get('id',1)
##        print 'tag:',e.tag
##        if e.get('id',1)=='references':
##            print '\n*** references found'
        if e.tag.endswith('}g'):
#            print 'tag ..}g'
            if self.targeting_state == 'find':
                if e.get('id',1) == self.group_id_target:
                    print '*** found group_id_target'
                    target_mark = True
                    self.targeting_state = 'found_and_loading'
                else:
                    #print 'discarded id:',e.get('id',1)
                    return
            elif self.targeting_state != 'found_and_loading':
                # no need to enter any 'g' thats not under the desired g
                print '*** discarded'
                return
        #print 'going into id, tag:',e.get('id',1), e.tag
        default = object()
        self.fill = parse_color(e.get('fill'), default)
        self.stroke = parse_color(e.get('stroke'), default)
        oldopacity = self.opacity
        self.opacity *= float(e.get('opacity', 1))
        fill_opacity = float(e.get('fill-opacity', 1))
        stroke_opacity = float(e.get('stroke-opacity', 1))
        
        oldtransform = self.transform
        self.transform = self.transform * Matrix(e.get('transform'))
        style = e.get('style')
        if style:
            sdict = parse_style(style)
            if 'fill' in sdict:
                self.fill = parse_color(sdict['fill'])
            if 'fill-opacity' in sdict:
                fill_opacity *= float(sdict['fill-opacity'])
            if 'stroke' in sdict:
                self.stroke = parse_color(sdict['stroke'])
            if 'stroke-opacity' in sdict:
                stroke_opacity *= float(sdict['stroke-opacity'])
        if self.fill == default:
            self.fill = [0, 0, 0, 255]
        if self.stroke == default:
            self.stroke = [0, 0, 0, 0]  
        if isinstance(self.stroke, list):
            self.stroke[3] = int(self.opacity * stroke_opacity * self.stroke[3])
        if isinstance(self.fill, list):
            self.fill[3] = int(self.opacity * fill_opacity * self.fill[3])                 
        if isinstance(self.stroke, list) and self.stroke[3] == 0:
            self.stroke = self.fill #Stroked edges antialias better
        
        if e.tag.endswith('rect'):
            x = float(e.get('x'))
            y = float(e.get('y'))
            h = float(e.get('height'))
            w = float(e.get('width'))
            center = complex(x+w/2.0, y+h/2.0)
##            print 'x,y,center:',x,y,center
##            print '+++ rect, fill:',self.fill
            color4 = tuple(self.fill)
            if color4 not in self.objects_by_color:
                self.objects_by_color[color4] = []
                print 'found color:', color4
            self.objects_by_color[color4].append((center,w,h))
            if color4 == self.terrain_color4:
                # reference rect
                print '*** terrain found ***'
                self.offset =  complex(x,y)
                self.w_ref = w
                self.h_ref = h
        
        for c in e.getchildren():
            try:
                self.parse_element(c)
            except Exception, ex:
                print 'Exception while parsing element', c
                raise
        self.transform = oldtransform
        self.opacity = oldopacity
        if target_mark:
            self.targeting_state = 'captured'
            self.target_found = True

    def parse_float(self, txt):
        if len(txt)>2:
            if not txt[-1].isdigit() and not txt[-2].isdigit():
                # probably units like px,cm,mm,... see unit tab in inkscape
                return float(txt[:-2])
        return float(txt)

def test_plane_select():
    path = '../data/levels'
    sname = '00.full.svg'
    SVG_special(path + '/' + sname,'actors')

if __name__ == '__main__':
    test_plane_select()
    
# nota: el bitmap lo esta tomando como un color
